package org.zetaframework.swagger;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.tags.Tag;
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
import org.springdoc.core.customizers.GlobalOperationCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.zetaframework.core.properties.SwaggerProperties;
import org.zetaframework.core.properties.TokenProperties;

import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * Swagger 配置
 *
 * @author gcc
 */
@Configuration
@EnableConfigurationProperties({SwaggerProperties.class, TokenProperties.class})
public class SwaggerConfiguration {

    public static final Pattern PATTERN_NUMBER = Pattern.compile("^(\\d+)");

    private final SwaggerProperties swaggerProperties;
    private final TokenProperties tokenProperties;
    public SwaggerConfiguration(SwaggerProperties swaggerProperties, TokenProperties tokenProperties) {
        this.swaggerProperties = swaggerProperties;
        this.tokenProperties = tokenProperties;
    }

    /**
     * 自定义GlobalOperationCustomizer
     * <br>
     * 说明：
     * <pre>
     *    为每个接口配置SecurityRequirement，方便请求自动携带token。
     *    如果某个接口不想携带token，可以在方法上添加@SecurityRequirements注解。注意最末尾有个s
     * </pre>
     */
    @Bean
    public GlobalOperationCustomizer globalOperationCustomizer() {
        return (operation, handlerMethod) -> {
            // 未被@SecurityRequirements注解的接口自动添加SecurityRequirement配置。
            // 说明：被@SecurityRequirements注解的接口，operation.security的值是一个不可变的list，如果往里面添加数据会抛出异常
            if (operation.getSecurity() == null) {
                operation.addSecurityItem(new SecurityRequirement().addList(tokenProperties.getTokenName()));
            }
            return operation;
        };
    }

    /**
     * SpringDoc OpenApi配置
     */
    @Bean
    public OpenAPI customOpenApi() {
        // 配置swagger文档信息
        OpenAPI openapi = new OpenAPI()
                .components(new Components().addSecuritySchemes(tokenProperties.getTokenName(), securityScheme()))
                .info(apiInfo())
                .externalDocs(externalDocs());

        // 获取网关服务地址
        List<SwaggerProperties.Server> serversItem = swaggerProperties.getServersItem();
        if (serversItem != null && !serversItem.isEmpty()) {
            // 添加网关服务地址
            for (SwaggerProperties.Server item : serversItem) {
                Server server = new Server();
                server.setUrl(item.getUrl());
                server.setDescription(item.getDescription());
                openapi.addServersItem(server);
            }
        }
        return openapi;
    }

    /**
     * 配置swagger文档信息
     */
    private Info apiInfo() {
        return new Info()
                .title(swaggerProperties.getTitle())
                .version(swaggerProperties.getVersion())
                .description(swaggerProperties.getDescription())
                .termsOfService(swaggerProperties.getTermsOfServiceUrl())
                // 许可证信息
                .license(new License().name(swaggerProperties.getLicense()).url(swaggerProperties.getLicenseUrl()))
                // 联系人信息
                .contact(new Contact()
                        .name(swaggerProperties.getContact().getName())
                        .url(swaggerProperties.getContact().getEmail())
                        .email(swaggerProperties.getContact().getUrl())
                );
    }


    /**
     * 外部文档配置
     * <br>
     * 说明：<br>
     * 这个配置目前只在swagger-ui.html页面生效
     */
    private ExternalDocumentation externalDocs() {
        return new ExternalDocumentation()
                .description("GITHUB")
                .url("https://github.com/xia5800/zeta-java-cloud");
    }

    /**
     * <p>安全方案配置</p>
     *
     * 说明：
     * <pre>
     * 1.配置请求头中鉴权信息
     * 2.<a href="https://github.com/OAI/OpenAPI-Specification/blob/3.0.1/versions/3.0.1.md#security-scheme-object">文档</a>
     * </pre>
     */
    private SecurityScheme securityScheme() {
        return new SecurityScheme()
                .type(SecurityScheme.Type.APIKEY)
                .name(tokenProperties.getTokenName())
                .in(SecurityScheme.In.HEADER)
                .scheme("basic");
    }


    /**
     * <p>自定义GlobalOpenApiCustomizer</p>
     *
     * 说明：
     * <pre>
     * 解决swagger-ui分组排序问题。 前提：@Tag(description = "") description 必须有值
     * </pre>
     *
     * 分租排序生效的写法：
     * <pre>{@code
     *     @Tag(name = "登录认证", description = "1")          // 值越小排到越前面
     *     @Tag(name = "用户管理", description = "2")
     *     @Tag(name = "角色管理", description = "3.角色管理")  // 可以有中文
     *     @Tag(name = "菜单管理", description = "菜单管理")    // 没有数字，则按照字母排序
     * }</pre>
     */
    @Bean
    public GlobalOpenApiCustomizer sortTagsByDescription() {
        return openApi -> {
            // Tags排序 （分组排序）
            if (openApi.getTags() != null) {
                List<Tag> sortedTags = openApi.getTags().stream()
                        .sorted(Comparator.comparingInt(this::extractNumberFromDescription))
                        .collect(Collectors.toList());
                openApi.setTags(sortedTags);
            }
        };
    }

    /**
     * 从Tag的description中提取数字，用于排序
     *
     * @param tag Tag对象
     * @return 如果description以数字开头则返回该数字，否则返回Integer.MAX_VALUE（排在最后）
     */
    private int extractNumberFromDescription(Tag tag) {
        if (tag.getDescription() == null) {
            return Integer.MAX_VALUE;
        }

        // 使用正则表达式提取开头的数字
        Matcher matcher = PATTERN_NUMBER.matcher(tag.getDescription());
        if (matcher.find()) {
            try {
                return Integer.parseInt(matcher.group(1));
            } catch (NumberFormatException e) {
                return Integer.MAX_VALUE;
            }
        }

        // 如果没有找到数字，则返回最大值，使其排在最后
        return Integer.MAX_VALUE;
    }
}
